In a typical Java implementation, whenever a Java application is executed, an instance of a Java virtual machine (JVM) is also started to support the execution of the Java application. If x Java applications are run concurrently, there would be x JVM instances. Typically, each JVM instance is an independent process that is unaware of the other JVM's. As a result, the various JVM instances typically do not share anything with each other.
One of the structures managed by each JVM is a code cache. The code cache contains compiled code for the methods that have been compiled by that JVM. In Java, when a method has been executed repeatedly, the JVM compiles that method from Java bytecodes down to native code that can be executed by a processor (this compilation process is typically referred to as just-in-time or JIT compilation). By doing so, the JVM enables the method to be executed faster (rather than using a Java interpreter to execute the Java bytecodes of the method, the method is executed directly by the processor, which is faster). Once a method is compiled, the compiled code for that method is inserted into the code cache for that JVM. The JVM thereafter executes the method by accessing its code cache.
The code cache of a JVM occupies a portion of the JVM's virtual memory space. This portion of the JVM's virtual memory space is mapped by the operating system to a portion of physical memory. It is in the physical memory where the compiled code for the code cache is actually stored. As noted above, a JVM instance typically does not share anything with other JVM instances. As a result, the compiled code in the code caches of different JVM's is typically stored in different portions of physical memory. For example, the compiled code for the code cache of JVM 1 may be stored in a first portion of physical memory, while the compiled code for the code cache of JVM 2 may be stored in a different portion of physical memory.
It has been observed that there are many common methods that are compiled by every JVM. These methods may be, for example, core JVM methods that are invoked over and over again by every JVM. Because each JVM manages its own code cache, and because each code cache is mapped to a different portion of physical memory, there are multiple copies of the compiled code for the common methods residing in different portions of physical memory. This redundancy leads to an unnecessary and wasteful consumption of the physical memory. In implementations where physical memory is quite limited (e.g. personal digital assistants, cellular phones, etc.), this inefficiency can be a significant problem. To optimize efficiency and performance, it would be better to store the compiled code for the common methods in only one portion of physical memory and have all of the JVM instances share (i.e. have their code caches map to) that same portion of physical memory.
One approach that has been implemented to enable multiple JVM instances to share the same physical memory portion is known as “cloning”. With cloning, a master JVM is first instantiated. The master JVM then compiles a list of common methods and inserts the compiled code for these common methods into the master JVM's code cache. That code cache (more specifically, the virtual memory range that makes up the code cache) is then mapped by the operating system to a portion of physical memory, and the compiled code for the common methods is stored into that portion of physical memory. Then, one or more “clones” of the master JVM are made (this may be done, for example, by using a “fork” instruction in Unix). Created in this way, each clone is still an independent JVM instance, but the code caches of all of the clones will be mapped to the portion of physical memory in which the compiled code for the common methods is already stored. In this way, the code caches of the master JVM and the JVM clones will all share the same portion of physical memory.
While cloning does enable multiple JVM's to share the same physical memory portion, it does have some significant drawbacks. One of the drawbacks is that cloning is operating system dependent. Only a few operating systems, such as Unix, have a “fork” or equivalent instruction. Thus, cloning can be implemented only on those operating systems. Another drawback is that cloning requires the master JVM to compile the common methods every time the master JVM is instantiated. If the list of common methods is relatively long, this compilation process may take a significant amount of time to carry out. This in turn will slow down execution and performance. For these and other reasons, cloning is not a wholly satisfactory method for enabling JVM's to share physical memory.